package com.zastoupil.pgc.blueprint_pattern.random;

import java.util.Random;

/**
 * Utility class that provides functionality related to pseudo-random number
 * generation. Implements the singleton pattern.
 * 
 * @author Peter Zastoupil
 * 
 */
public class RandomUtils {

	private Random rng;
	private static RandomUtils _instance;

	private RandomUtils() {
		this.rng = new Random(System.currentTimeMillis());
	}

	/**
	 * Required by the singleton pattern. Returns the singleton instance of
	 * RandomUtils
	 * 
	 * @return the singleton instance of RandomUtils
	 */
	public static RandomUtils getInstance() {
		if (_instance == null) {
			_instance = new RandomUtils();
		}
		return _instance;
	}

	/**
	 * Sets the seed of this RandomUtils internal random number generator
	 * 
	 * @param seed
	 *            the seed to set
	 */
	public void setSeed(long seed) {
		this.rng.setSeed(seed);
	}

	/**
	 * Sets the seed of this RandomUtils internal random number generator. Uses
	 * the hash code of the object passed as a seed
	 * 
	 * @param seed
	 *            the Object whose hash code should be used as a seed
	 * @return the seed that was set, as a long value
	 */
	public long setSeed(Object seed) {

		long hash = seed.hashCode();

		this.setSeed(hash);

		return hash;

	}

	/**
	 * 
	 * @return a pseudo-random boolean value based on the random number
	 *         generator's seed
	 */
	public boolean randomBoolean() {

		return this.rng.nextBoolean();

	}

	/**
	 * 
	 * @return a pseudo-random integer value based on the random number
	 *         generator's seed
	 */
	public int randomInt() {

		return this.rng.nextInt();

	}

	/**
	 * Returns a pseudo-random integer value between zero (inclusive) and the
	 * given maximum value (exclusive)
	 * 
	 * @param max
	 *            the maximum value to be used in the integer generation
	 * @return a pseudo-random integer value between zero (inclusive) and the
	 *         given max (exclusive)
	 */
	public int randomInt(int max) {

		return this.rng.nextInt(max);

	}

	/**
	 * Returns a pseudo-random integer value between the given minimum value
	 * (inclusive) and the given maximum value (exclusive)
	 * 
	 * @param min
	 *            the minimum value to be used in the integer generation
	 * @param max
	 *            the maximum value to be used in the integer generation
	 * @return a pseudo-random integer value between the given minimum value
	 *         (inclusive) and the given maximum value (exclusive)
	 */
	public int randomInt(int min, int max) {

		int range = max - min;

		return this.rng.nextInt(range) + min;

	}

	/**
	 * 
	 * @return a pseudo-random long value based on the random number generator's
	 *         seed
	 */
	public long randomLong() {

		return this.rng.nextLong();

	}

	/**
	 * Returns a pseudo-random float value between 0.0f (inclusive) and 1.0f
	 * (exclusive)
	 * 
	 * @return a pseudo-random float value between 0.0f (inclusive) and 1.0f
	 *         (exclusive)
	 */
	public float randomFloat() {

		return this.rng.nextFloat();

	}

	/**
	 * Returns a pseudo-random float value between 0.0f (inclusive) and the
	 * given maximum value (exclusive)
	 * 
	 * @param max
	 *            the maximum value to be used in the float generation
	 * @return a pseudo-random float value between 0.0f (inclusive) and the
	 *         given maximum value (exclusive)
	 */
	public float randomFloat(float max) {

		return this.rng.nextFloat() * max;

	}

	/**
	 * Returns a pseudo-random float value between the given minimum value
	 * (inclusive) and the given maximum value (exclusive)
	 * 
	 * @param min
	 *            the minimum value to be used in the float generation
	 * @param max
	 *            the maximum value to be used in the float generation
	 * @return a pseudo-random float value between the given minimum value
	 *         (inclusive) and the given maximum value (exclusive)
	 */
	public float randomFloat(float min, float max) {

		float range = max - min;

		return (this.rng.nextFloat() * range) + min;

	}

	/**
	 * Returns a pseudo-random double value between 0.0d (inclusive) and 1.0d
	 * (exclusive)
	 * 
	 * @return a pseudo-random double value between 0.0d (inclusive) and 1.0d
	 *         (exclusive)
	 */
	public double randomDouble() {

		return this.rng.nextDouble();

	}

	/**
	 * Returns a pseudo-random double value between 0.0d (inclusive) and the
	 * given maximum value (exclusive)
	 * 
	 * @param max
	 *            the maximum value to be used in the double generation
	 * @return a pseudo-random double value between 0.0d (inclusive) and the
	 *         given maximum value (exclusive)
	 */
	public double randomDouble(double max) {

		return this.rng.nextDouble() * max;

	}

	/**
	 * Returns a pseudo-random double value between the given minimum value
	 * (inclusive) and the given maximum value (exclusive)
	 * 
	 * @param min
	 *            the minimum value to be used in the double generation
	 * @param max
	 *            the maximum value to be used in the double generation
	 * @return a pseudo-random double value between the given minimum value
	 *         (inclusive) and the given maximum value (exclusive)
	 */
	public double randomDouble(double min, double max) {

		double range = max - min;

		return (this.rng.nextDouble() * range) + min;

	}

	/**
	 * Returns an array of bytes of a given length with randomly generated
	 * contents
	 * 
	 * @param length
	 *            the length of the array to return (or the number of bytes to
	 *            generate)
	 * @return an array of bytes of a given length with randomly generated
	 *         contents
	 */
	public byte[] randomBytes(int length) {

		byte[] result = new byte[length];

		this.rng.nextBytes(result);

		return result;

	}

}
